# @Version        : 1.0
# @Update Time    : 2025/10/18 10:34
# @File           : config.py
# @IDE            : PyCharm
# @Desc           : 文件描述信息
import os
from dataclasses import dataclass, field
from functools import lru_cache
from typing import Any, Dict, Literal, Optional

from pydantic_settings import BaseSettings, SettingsConfigDict

from authx.core.enums import ReplacedRange, LogoutMode, LogoutRange, TokenStyle


class TokenConfig(BaseSettings):
    """
    Sa-Token 全局配置类
    """

    TOKEN_URL: str = "/auth/api/login"
    # /** token 名称 （同时也是： cookie 名称、提交 token 时参数的名称、存储 token 时的 key 前缀） */
    TOKEN_NAME: str = "fa_token"
    # /** token 有效期（单位：秒） 默认30天，-1 代表永久有效 */
    TIMEOUT: int = 60 * 60 * 24 * 30
    # * token 最低活跃频率（单位：秒），如果 token 超过此时间没有访问系统就会被冻结，默认-1 代表不限制，永不冻结
    # * （例如可以设置为 1800 代表 30 分钟内无操作就冻结）
    ACTIVE_TIMEOUT: int = -1
    # * 是否启用动态 activeTimeout 功能，如不需要请设置为 false，节省缓存请求次数
    DYNAMIC_ACTIVE_TIMEOUT: bool = False
    # * 是否允许同一账号多地同时登录 （为 true 时允许一起登录, 为 false 时新登录挤掉旧登录）
    IS_CONCURRENT: bool = True
    # * 在多人登录同一账号时，是否共用一个 token （为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token）
    IS_SHARE: bool = False
    # * 当 isConcurrent=false 时，顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
    REPLACED_RANGE: ReplacedRange = ReplacedRange.CURR_DEVICE_TYPE
    # * 同一账号最大登录数量，-1代表不限 （只有在 isConcurrent=true, isShare=false 时此配置项才有意义）
    MAX_LOGIN_COUNT: int = 12
    # 溢出 maxLoginCount 的客户端，将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
    LOGOUT_MODE: LogoutMode = LogoutMode.LOGOUT
    # * 在每次创建 token 时的最高循环次数，用于保证 token 唯一性（-1=不循环尝试，直接使用）
    MAX_TRY_TIMES: int = 12
    # * 是否尝试从请求体里读取 token
    IS_READ_QUERY: bool = True
    # 是否尝试从 header 里读取 token
    IS_READ_HEADER: bool = True
    # * 是否尝试从 cookie 里读取 token
    IS_READ_COOKIE: bool = True
    # * 是否为持久Cookie（临时Cookie在浏览器关闭时会自动删除，持久Cookie在重新打开后依然存在）
    IS_LASTING_COOKIE: bool = True
    # * 是否在登录后将 token 写入到响应头
    IS_WRITE_HEADER: bool = False
    # * 注销范围 (TOKEN=只注销当前 token 的会话，ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
    # * <br/> (此参数只在调用 StpUtil.logout() 时有效)
    LOGOUT_RANGE: LogoutRange = LogoutRange.TOKEN
    # * 如果 token 已被冻结，是否保留其操作权 (是否允许此 token 调用注销API)
    # * <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
    IS_LOGOUT_KEEP_FREEZE_OPS: bool = False
    # * 在注销 token 后，是否保留其对应的 Token-Session
    IS_LOGOUT_KEEP_TOKEN_SESSION: bool = False

    # 在登录时，是否立即创建对应的 Token-Session （true=在登录时立即创建，false=在第一次调用 getTokenSession() 时创建）
    IS_RIGHT_NOW_CREATE_TOKEN_SESSION: bool = False
    # * token 风格（默认可取值：uuid、simple-uuid、random-32、random-64、random-128、tik）
    TOKEN_STYLE: TokenStyle = TokenStyle.UUID

    # * 默认 TokenDao 实现类中，每次清理过期数据间隔的时间（单位: 秒），默认值30秒，设置为 -1 代表不启动定时清理
    DATA_REFRESH_PERIOD: int = 30
    # 获取 Token-Session 时是否必须登录（如果配置为true，会在每次获取 getTokenSession() 时校验当前是否登录）
    TOKEN_SESSION_CHECK_LOGIN: bool = True
    # * 是否打开自动续签 activeTimeout （如果此值为 true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作）
    AUTO_RENEW: bool = True
    # * token 前缀, 前端提交 token 时应该填写的固定前缀，格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx)
    TOKEN_PREFIX: str = "Bearer"
    # * cookie 模式是否自动填充 token 前缀
    COOKIE_AUTO_FILL_PREFIX: bool = False
    # * 是否在初始化配置时在控制台打印版本字符画
    IS_PRINT: bool = True
    # * 是否打印操作日志
    IS_LOG: bool = False
    # * 日志等级（trace、debug、info、warn、error、fatal），此值与 logLevelInt 联动
    LOG_LEVEL: str = "trace"
    # * 日志等级 int 值（1=trace、2=debug、3=info、4=warn、5=error、6=fatal），此值与 logLevel 联动
    LOG_LEVEL_INT: int = 1
    # * 是否打印彩色日志
    IS_COLOR_LOG: bool = False
    # * jwt秘钥（只有集成 jwt 相关模块时此参数才会生效）
    JWT_SECRET_KEY: str
    # Http Basic 认证的默认账号
    HTTP_BASIC_USERNAME: str = "authx"
    # Http Basic 认证的默认密码，例如：123456
    HTTP_BASIC_PASSWORD: str = "123456"
    # * Http Digest 认证的默认账号和密码，冒号隔开，例如：sa:123456
    HTTP_DIGEST: str = ""
    # * 配置当前项目的网络访问地址
    CURR_DOMAIN: str = ""
    # * Same-Token 的有效期 (单位: 秒)
    SAME_TOKEN_TIMEOUT: int = 60 * 60 * 24
    # * 是否校验 Same-Token（部分rpc插件有效）
    CHECK_SAME_TOKEN: bool = False
    # * Cookie配置对象
    COOKIE_DOMAIN: Optional[str] = None
    COOKIE_PATH: str = "/"
    COOKIE_SECURE: bool = False
    COOKIE_HTTP_ONLY: bool = True
    COOKIE_SAME_SITE: Literal["lax", "strict", "none"] = "lax"
    COOKIE_EXTRA_ATTRS: Optional[Dict[str, Any]] = None
    # 配置允许使用其他类型
    model_config = SettingsConfigDict(
        extra="ignore",
        env_file=".env",
        env_file_encoding="utf-8",
    )


@lru_cache()
def get_token_config() -> TokenConfig:
    return TokenConfig()


settings = get_token_config()


@dataclass
class CookieConfig:
    domain: str = field(default="")
    path: str = field(default="/")
    secure: bool = field(default=False)
    http_only: bool = field(default=True)
    same_site: Literal["lax", "strict", "none"] = field(default="lax")
    extra_attrs: Dict[str, Any] = field(default_factory=dict)


# ---- 领域配置：运行期传给 StpLogic（不可变）----
@dataclass(frozen=True)
class AuthConfig:
    token_name: str
    timeout: int
    active_timeout: int
    dynamic_active_timeout: bool
    is_concurrent: bool
    is_share: bool
    replaced_range: ReplacedRange
    max_login_count: int
    logout_mode: LogoutMode
    max_try_times: int
    is_read_query: bool
    is_read_header: bool
    is_read_cookie: bool
    is_lasting_cookie: bool
    is_write_header: bool
    logout_range: LogoutRange
    is_logout_keep_freeze_ops: bool
    is_logout_keep_token_session: bool
    is_right_now_create_token_session: bool
    token_style: TokenStyle
    data_refresh_period: int
    token_session_check_login: bool
    auto_renew: bool
    token_prefix: str
    cookie_auto_fill_prefix: bool
    is_print: bool
    is_log: bool
    log_level: str
    log_level_int: int
    is_color_log: bool
    jwt_secret_key: str
    http_basic: str
    http_digest: str
    curr_domain: str
    same_token_timeout: int
    check_same_token: bool
    cookie_domain: Optional[str]
    cookie_path: str
    cookie_secure: bool
    cookie_http_only: bool
    cookie_same_site: Literal["lax", "strict", "none"]
    cookie_extra_attrs: Optional[Dict[str, Any]]
    cookie_config: CookieConfig


def to_domain_config(s: TokenConfig | None = None) -> AuthConfig:
    s = s or settings
    return AuthConfig(
        token_name=s.TOKEN_NAME,
        timeout=s.TIMEOUT,
        active_timeout=s.ACTIVE_TIMEOUT,
        dynamic_active_timeout=s.DYNAMIC_ACTIVE_TIMEOUT,
        is_concurrent=s.IS_CONCURRENT,
        is_share=s.IS_SHARE,
        replaced_range=s.REPLACED_RANGE,
        max_login_count=s.MAX_LOGIN_COUNT,
        logout_mode=s.LOGOUT_MODE,
        max_try_times=s.MAX_TRY_TIMES,
        is_read_query=s.IS_READ_QUERY,
        is_read_header=s.IS_READ_HEADER,
        is_read_cookie=s.IS_READ_COOKIE,
        is_lasting_cookie=s.IS_LASTING_COOKIE,
        is_write_header=s.IS_WRITE_HEADER,
        logout_range=s.LOGOUT_RANGE,
        is_logout_keep_freeze_ops=s.IS_LOGOUT_KEEP_FREEZE_OPS,
        is_logout_keep_token_session=s.IS_LOGOUT_KEEP_TOKEN_SESSION,
        is_right_now_create_token_session=s.IS_RIGHT_NOW_CREATE_TOKEN_SESSION,
        token_style=s.TOKEN_STYLE,
        data_refresh_period=s.DATA_REFRESH_PERIOD,
        token_session_check_login=s.TOKEN_SESSION_CHECK_LOGIN,
        auto_renew=s.AUTO_RENEW,
        token_prefix=s.TOKEN_PREFIX,
        cookie_auto_fill_prefix=s.COOKIE_AUTO_FILL_PREFIX,
        is_print=s.IS_PRINT,
        is_log=s.IS_LOG,
        log_level=s.LOG_LEVEL,
        log_level_int=s.LOG_LEVEL_INT,
        is_color_log=s.IS_COLOR_LOG,
        jwt_secret_key=s.JWT_SECRET_KEY,
        http_basic=s.HTTP_BASIC_USERNAME,
        http_digest=s.HTTP_DIGEST,
        curr_domain=s.CURR_DOMAIN,
        same_token_timeout=s.SAME_TOKEN_TIMEOUT,
        check_same_token=s.CHECK_SAME_TOKEN,
        cookie_domain=s.COOKIE_DOMAIN,
        cookie_path=s.COOKIE_PATH,
        cookie_secure=s.COOKIE_SECURE,
        cookie_http_only=s.COOKIE_HTTP_ONLY,
        cookie_same_site=s.COOKIE_SAME_SITE,
        cookie_extra_attrs=s.COOKIE_EXTRA_ATTRS,
        cookie_config=CookieConfig(
            domain=s.COOKIE_DOMAIN,
            path=s.COOKIE_PATH,
            secure=s.COOKIE_SECURE,
            http_only=s.COOKIE_HTTP_ONLY,
            same_site=s.COOKIE_SAME_SITE,
            extra_attrs=s.COOKIE_EXTRA_ATTRS,
        ),
    )


# ---- 每个 login_type 的差异化覆盖项（None 表示不覆盖）----
@dataclass(frozen=True)
class LogicOverrides:
    token_name: Optional[str] = None
    timeout: Optional[int] = None
    active_timeout: Optional[int] = None
    dynamic_active_timeout: Optional[bool] = None
    is_concurrent: Optional[bool] = None
    is_share: Optional[bool] = None
    replaced_range: Optional[ReplacedRange] = None
    max_login_count: Optional[int] = None
    logout_mode: Optional[LogoutMode] = None
    max_try_times: Optional[int] = None
    is_read_body: Optional[bool] = None
    is_read_header: Optional[bool] = None
    is_read_cookie: Optional[bool] = None
    is_lasting_cookie: Optional[bool] = None
    is_write_header: Optional[bool] = None
    logout_range: Optional[LogoutRange] = None
    is_logout_keep_freeze_ops: Optional[bool] = None
    is_logout_keep_token_session: Optional[bool] = None
    is_right_now_create_token_session: Optional[bool] = None
    token_style: Optional[TokenStyle] = None
    data_refresh_period: Optional[int] = None
    token_session_check_login: Optional[bool] = None
    auto_renew: Optional[bool] = None
    token_prefix: Optional[str] = None
    cookie_auto_fill_prefix: Optional[bool] = None
    is_print: Optional[bool] = None
    is_log: Optional[bool] = None
    log_level: Optional[str] = None
    log_level_int: Optional[int] = None
    is_color_log: Optional[bool] = None
    jwt_secret_key: Optional[str] = None
    http_basic: Optional[str] = None
    http_digest: Optional[str] = None
    curr_domain: Optional[str] = None
    same_token_timeout: Optional[int] = None
    check_same_token: Optional[bool] = None
    # 注意：像 cookie_domain 这种“值可能为 None”的字段，无法用 None 代表“覆盖成 None”
    # 这里保持 None=不覆盖；如需“覆盖成 None”，可引入 UNSET 哨兵值，见后面的备注。
    cookie_domain: Optional[str] = None
    cookie_path: Optional[str] = None
    cookie_secure: Optional[bool] = None
    cookie_http_only: Optional[bool] = None
    cookie_same_site: Optional[Literal["lax", "strict", "none"]] = None
    cookie_extra_attrs: Optional[Dict[str, Any]] = None


def merge_config(base: AuthConfig, ov: Optional[LogicOverrides]) -> AuthConfig:
    if ov is None:
        return base
    data = base.__dict__.copy()
    for k, v in ov.__dict__.items():
        if v is not None:
            data[k] = v
    return AuthConfig(**data)
